// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include <memory>
#include <string>
#include <type_traits>
#include <vector>

/**
 * Meta class of entity types for simple rtti checks.
 */
class FWorldEntityMeta
	: public std::enable_shared_from_this<FWorldEntityMeta>
{

public:
	using Ptr = std::shared_ptr<FWorldEntityMeta>;
	using ConstPtr = std::shared_ptr<const FWorldEntityMeta>;

	std::string			ClassName;
	Ptr					Parent;
	std::vector<Ptr>	Children;

public:

	FWorldEntityMeta(const std::string& name, const Ptr& parent);

	bool IsA(const FWorldEntityMeta& other) const;

};

/**
 * Iterator to walk up the meta tree.
 */
class FWorldEntityMetaUpIterator
{
	FWorldEntityMeta::ConstPtr	_current;

public:
	FWorldEntityMetaUpIterator(const FWorldEntityMeta& meta)
		: _current(meta.shared_from_this())
	{}
	bool IsDone() const { return _current == nullptr; }
	void Next() { _current = _current->Parent; }
	const FWorldEntityMeta& operator*() const { return *_current; }
};

/**
 * Helper class to force init meta on startup.
 */
template<typename EntityType>
class FWorldEntityMetaInit
{
public:
	FWorldEntityMetaInit()
	{
		// Init the meta singleton to avoid potential race conditions.
		EntityType::sMetaClass();
	}
};

/**
 * Helper class to define meta.
 */
template<typename EntityType, typename ParentType>
class FWorldEntityMetaOwner
{
	FWorldEntityMeta::Ptr	_meta;

public:
	FWorldEntityMetaOwner(const std::string& name)
	{
		static_assert(std::is_base_of<ParentType, EntityType>::value, "Bad inheritence");
		if (!std::is_same<EntityType, ParentType>::value)
		{
			FWorldEntityMeta::Ptr parent =
				const_cast<FWorldEntityMeta&>(ParentType::sMetaClass()).shared_from_this();
			_meta = std::make_shared<FWorldEntityMeta>(name, parent);
			parent->Children.push_back(_meta);
		}
		else
		{
			// i.e. FWorldEntityPart class
			_meta = std::make_shared<FWorldEntityMeta>(name, nullptr);
		}
	}

	const FWorldEntityMeta& Meta() const
	{
		return *_meta;
	}
};

#define WORLD_ENTITY_META_DECLARE(ClassName) \
	static const FWorldEntityMeta& sMetaClass(); \
	virtual const FWorldEntityMeta& MetaClass() const;

#define WORLD_ENTITY_META_DEFINE(ClassName, ParentName) \
	FWorldEntityMetaInit<ClassName> gs##ClassName##MetaInit; \
	const FWorldEntityMeta& ClassName::sMetaClass() { \
		static FWorldEntityMetaOwner<ClassName, ParentName> sOwner(#ClassName); \
		return sOwner.Meta(); \
	} \
	const FWorldEntityMeta& ClassName::MetaClass() const { \
		return ClassName::sMetaClass(); \
	}
